ย - Last update: 2023-07-15

SIMD์— ๊ด€ํ•œ ๊ฐ„๋‹จํ•œ ์ •๋ฆฌ. ์•Œ๊ฒŒ ๋˜์—ˆ๋Š”๋ฐ ์ •๋ฆฌ ์•ˆํ•˜๋ฉด ๋‚˜์ค‘์— ๋‹ค์‹œ ๋งจ๋•…์—์„œ ๊ตฌ๊ธ€๋ง ํ•ด์•ผํ•˜๋ฏ€๋กœ ํ•œ๋ฒˆ ์•Œ๊ฒŒ ๋œ ๋‚ด์šฉ ์ •๋ฆฌํ•˜๊ณ  ๊ฐ€๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

SIMD

SIMD๋Š” Single Instruction Multiple Data์˜ ์•ฝ์ž์ด๋‹ค. Vectorization๊ณผ ๊ด€๋ จ์žˆ๋Š”๋ฐ, ๋ณดํ†ต ๊ทธ๋ž˜ํ”ฝ ์„ธ๊ณ„์—์„œ๋Š” ํ•˜๋‚˜์˜ Instruction์œผ๋กœ ์—ฌ๋Ÿฌ๊ฐœ์˜ ์œ„์น˜์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋™์‹œ์— ์กฐ์ž‘ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ƒ‰์ƒ์„ ๋ฐ˜์ „์‹œํ‚ค๋Š” operation์˜ ๊ฒฝ์šฐ ์ „์ฒด ํ”ฝ์…€์— ๋Œ€ํ•ด ์ƒ‰์ƒ ๋น„ํŠธ๋ฅผ ๋’ค์ง‘์–ด์•ผ ํ•  ๊ฒƒ์ด๋‹ค. ์ด๋Ÿฌํ•œ ์ž‘์—…๋“ค์€ ๊ผญ ๊ทธ๋ž˜ํ”ฝ์— ๊ตญํ•œ๋˜์ง€ ์•Š๋”๋ผ๋„, ์ผ๋ฐ˜์ ์œผ๋กœ ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋“ค์ด ๋งŽ์ด ์žˆ๋‹ค. ๋จธ์‹ ๋Ÿฌ๋‹์—์„œ๋ผ๋˜์ง€.. ๊ทธ๋ž˜์„œ ์˜ˆ์ „๋ถ€ํ„ฐ majorํ•œ CPU๋“ค์—์„œ ์ง€์›์ด ์ด๋ฃจ์–ด์ง€๊ณ  ์žˆ์œผ๋ฉฐ, Intel CPU๋Š” ์ด๋ฅผ avx ๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์ง€์›ํ•œ๋‹ค.

AVX

avx๋Š” Advanced Vector Extensions์˜ ์•ฝ์ž์ด๋‹ค. ์ฒ˜์Œ์—๋Š” 128 bits ๋‹จ์œ„์˜ ๋ณ‘๋ ฌ์—ฐ์‚ฐ์ด ์ง€์›๋˜๋‹ค๊ฐ€, ํ˜„์žฌ ๊ฐ์ข… ์˜จ๋ผ์ธ PS ์‚ฌ์ดํŠธ๋“ค(ex: codeforces, BOJ)์˜ ๊ฒฝ์šฐ avx2, 256 bits ๋‹จ์œ„๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ์ตœ๊ทผ์— ์ข‹์€ Intel CPU๋Š” 512 bits๊นŒ์ง€๋„ ์ง€์›์ด ๋œ๋‹ค.

๋ง๋งŒ ๋“ค์–ด์„œ๋Š” ๋ณ‘๋ ฌ์—ฐ์‚ฐ ์ฒด๊ฐ์ด ์ž˜ ์•ˆ๋  ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ตฌ์ฒด์ ์ธ ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด๋ณด๋ฉด 256 bits ๋‹จ์œ„๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ๋ฅผ ๊ธฐ์ค€์œผ๋กœ 16๋น„ํŠธ ์ •์ˆ˜(short)๋ฅผ ๊ธฐ๋ณธ ์ž๋ฃŒํ˜•์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ๋™์‹œ์— size๊ฐ€ 16์ธ vector์— ์—ฐ์‚ฐ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๋œป์ด๋‹ค. ์‹œ๊ฐ„๋ณต์žก๋„๊ฐ€ ์ •์งํ•˜๊ฒŒ O(N/16)O(N/16)์œผ๋กœ ์ค„์–ด๋“œ๋Š” ํšจ๊ณผ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๊ฒ ๋‹ค. bitset์„ ๋น„์Šทํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ๋А๋‚Œ์ด ์žˆ๋‹ค. ์ด๊ฒƒ๋„ ์ƒ์ˆ˜์‹œ๊ฐ„์„ ์ค„์ด๊ธฐ ์œ„ํ•ด ํ™œ์šฉ๋˜๋Š” ๊ฒฝ์šฐ๋“ค์ด ์žˆ์–ด์„œ..

์‚ฌ์šฉ ๋ฐฉ๋ฒ•

์ผ๋‹จ ์ด ๊ธ€์—์„œ๋Š” immintrin.h๋ฅผ include ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ(aka ์†์‹ฌ๋“œ)์„ ๊ธฐ์ค€์œผ๋กœ ํ•œ๋ฒˆ ์•Œ์•„๋ณด๊ฒ ๋‹ค. Auto Vectorization์ด๋ž€ ๊ฒƒ๋„ ์žˆ๋Š”๋ฐ, ์ด๊ฒƒ์€ ๊ทธ๋ƒฅ ์ปดํŒŒ์ผ ์˜ต์…˜์œผ๋กœ ์ผœ๋ฉด ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์•Œ์•„์„œ ์ตœ์ ํ™” ํ•ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค. ์ด๊ฑฐ๋Š” ๊ทธ๋ƒฅ ํ”Œ๋ž˜๊ทธ๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋˜๋Š” ๊ฒƒ์ด๋ผ ๋”ฐ๋กœ ๊ธ€๋กœ ๋‹ค๋ฃจ๊ธฐ์—๋„ ์• ๋งคํ•œ ์˜์—ญ์ด๋ผ..

์•„๋งˆ PS์— ์‹ฌ์ทจํ•˜๋‹ค๊ฐ€ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ header๋ฅผ ์ข…์ข… ๋ณด๊ณค ํ–ˆ์„ ๊ฒƒ์ด๋‹ค.

#pragma GCC optimize ("O3")
#pragma GCC optimize ("Ofast")
#pragma GCC optimize ("unroll-loops")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,avx,avx2")

์ด๊ฒƒ์€ ์‚ฌ์‹ค PS ์‹œ์Šคํ…œ์—์„œ ๋ง‰์œผ๋ ค๋ฉด ๋ง‰์„ ์ˆ˜๋„ ์žˆ๋Š” ๋ถ€๋ถ„์ธ๋ฐ, ์ปดํŒŒ์ผ๋Ÿฌ์—๊ฒŒ ๊ฐ•์ œ๋กœ ์–ด๋–ค ๊ธฐ๋Šฅ์„ ์ผค ๊ฒƒ์ธ์ง€์— ๋Œ€ํ•ด์„œ pragma ํ”Œ๋ž˜๊ทธ๋ฅผ ํ†ตํ•ด์„œ ์•Œ๋ ค์ฃผ๋Š” ๊ฒƒ์ด๋‹ค. ์ด ์ค‘์—์„œ, avx, avx2๋ฅผ taget์œผ๋กœ ์ง€์ •ํ•˜๋Š” ๋ถ€๋ถ„์ด ์žˆ๋Š”๋ฐ, ์ด๊ฒƒ๊ณผ ๋”๋ถˆ์–ด, ์•„๋ž˜ header๋ฅผ include ํ•˜๋ฉด ์ด์ œ SIMD๋ฅผ ์‚ฌ์šฉํ•  ์ค€๋น„๊ฐ€ ๋๋‚œ๋‹ค.

#include <immintrin.h>

PS ์‚ฌ์šฉ ์˜ˆ์‹œ

์ด ๋ฌธ์ œ๋Š” ๊ฐ ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋˜๋Š” ์ˆœ์„œ๊ฐ€ ๋ฐ”๋€Œ๊ฒŒ ๋˜๋ฉด ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ”๋€Œ๊ธฐ ๋•Œ๋ฌธ์—, ์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ์ „์ฒด ๊ตฌ๊ฐ„์— ๋Œ€ํ•ด์„œ ๋น ๋ฅด๊ฒŒ โˆฃaโˆ’bโˆฃ|a - b| ์—ฐ์‚ฐ์„ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€๊ฐ€ ๊ด€๊ฑด์ด๊ณ , SIMD๋ฅผ ๋ผ์–น๊ธฐ ๋”ฑ ์ข‹์€ ์กฐ๊ฑด์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๊ฒ ๋‹ค. ์–ด๋–ค vector๊ฐ€ ์žˆ๋‹ค๋ฉด, ๋™์‹œ์— ๋บ„์…ˆ์„ ์ ์šฉํ•˜๋ฉด์„œ ์ ˆ๋Œ€๊ฐ’์„ ์ ์šฉํ•˜๋ฉด ๋˜๋Š”๋ฐ, ๋‘˜ ๋‹ค avx2์—์„œ ์ง€์›ํ•˜๋Š” ๋ช…๋ น์–ด์ด๋‹ค.

  • SIMD ๋ฐ Data Structure ์„ ์–ธ๋ถ€
#pragma GCC optimize ("O3")
#pragma GCC optimize ("Ofast")
#pragma GCC optimize ("unroll-loops")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,avx,avx2")
#include <bits/stdc++.h>
using namespace std;
#include <immintrin.h>
alignas(32) int bucket[400][512], TMP[8];
alignas(32) int bucketLazy[400][200'000];
alignas(32) int bucketLazySz[400];

์—ฌ๊ธฐ์„œ alignas(32) ๋ถ€๋ถ„์„ ์œ ์˜ํ•ด์„œ ๋ด์•ผํ•˜๋Š”๋ฐ, ์ด๊ฒƒ์€ SIMD๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์ œ์•ฝ์กฐ๊ฑด์ด๋ผ๊ณ  ๋ณด๋ฉด ๋˜๊ฒ ๋‹ค. ํ•ด๋‹น ์ž๋ฃŒํ˜•์˜ ํฌ๊ธฐ๋กœ align ๋˜์–ด์•ผ ํ•œ๋‹ค. ๋ฉ”๋ชจ๋ฆฌ์ƒ ํŠน์ • ์ฃผ์†Œ๋กœ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์•ผ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๊ผญ ๋„ฃ์–ด์ค˜์•ผ ํ•˜๋Š” ์˜ต์…˜์ด๋‹ค. ๋งŒ์•ฝ short๋ฅผ ์“ด๋‹ค๋ฉด alignas(16)์ด ๋˜๋ฉด ๋œ๋‹ค.

  • ํ’€์ด ์‚ฌ์šฉ
inline void handleLazy(int b) {
for(int j = 0; j < bucketLazySz[b]; ++j) {
int& d = bucketLazy[b][j];
*(__m256i*) TMP = _mm256_set_epi32(d, d, d, d, d, d, d, d);
int* w = bucket[b];
for(int i = 0; i < 64; ++i) {
*(__m256i*)w = _mm256_sub_epi32(*(__m256i*)w, *(__m256i*) TMP);
*(__m256i*)w = _mm256_abs_epi32(*(__m256i*)w);
w += 8;
}
}
bucketLazySz[b] = 0;
}

๋‚˜์˜ ๊ฒฝ์šฐ์—๋Š” ๊ฐ bucket์„ 512 ํฌ๊ธฐ๋กœ ๋‚˜๋ˆ ์„œ ๊ณ ์ •๋œ ํฌ๊ธฐ์˜ ๊ณต๊ฐ„์— Lazy์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•  ๋•Œ SIMD๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. ์›๋ž˜๊ฐ™์œผ๋ฉด ๊ฐ bucket๋ณ„๋กœ ๋ฃจํ”„๋ฅผ 512๋ฒˆ ๋Œ์•„์•ผ๊ฒ ์ง€๋งŒ, ์œ„์ฒ˜๋Ÿผ ํ•˜๋ฉด 8๋ฐฐ๋กœ ์ค„์–ด๋“  64๋ฒˆ๋งŒ์— ์ˆ˜ํ–‰์„ ์™„๋ฃŒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

์ฝ”๋“œ์— ๋‚˜ํƒ€๋‚˜๋Š” _mm256์œผ๋กœ ์‹œ์ž‘๋˜๋Š” ๊ฒƒ์€ 256 bits ๊ณต๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ ์—ฐ์‚ฐ์ด ์ผ์–ด๋‚œ๋‹ค๋Š” ๋œป์ด๋ฉฐ, ์ž…๋ ฅ๊ฐ’๊ณผ ์ถœ๋ ฅ๊ฐ’์ด ๋ชจ๋‘ 256 bits๋ผ๊ณ  ๋ณด๋ฉด ๋œ๋‹ค. int32์˜ ๊ฒฝ์šฐ 32 bits๋ฅผ ๋‹จ์œ„๋กœ ๊ฐ€์ง€๋Š” vector๊ฐ€ ๋˜๋ฏ€๋กœ 8๊ฐœ์”ฉ ์—ฐ์‚ฐ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค. O(N/8)O(N/8) ์ด ์ข€ ๋ฏธ๋ฌ˜ํ•˜์ง€ ์•Š์„๊นŒ ์ƒ๊ฐ๋˜๊ฒ ์ง€๋งŒ, 10์ดˆ ๊ฑธ๋ฆด๊ฒƒ์ด 1.25์ดˆ ๊ฑธ๋ฆฐ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ํšจ๊ณผ๊ฐ€ ์žˆ๋‹ค. (๋ฌผ๋ก  SIMD ์ž์ฒด์˜ overhead ๋•Œ๋ฌธ์— ์ด๋ณด๋‹ค๋Š” ํ–ฅ์ƒํญ์ด ์ ๊ฒ ์ง€๋งŒ..)

์‚ฌ์šฉ์€ ๋ฐฐ์—ด์˜ ํฌ์ธํ„ฐ์— (__m256i*)์™€ ๊ฐ™์€ ํฌ์ธํ„ฐ ๋ณ€ํ™˜์„ ์‚ฌ์šฉํ•˜๋ฉด ๋˜๊ณ , ์‹œ์ž‘ ์ฃผ์†Œ ๊ธฐ์ค€์œผ๋กœ 256 bits ๋งŒํผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ์—ฐ์‚ฐ์ด ์ด๋ฃจ์–ด์ง„๋‹ค. load ์—ฐ์‚ฐ๋„ ์กด์žฌํ•˜๋Š”๋ฐ, ๊ตณ์ด ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด __m256 ์ž๋ฃŒํ˜•์—๋‹ค๊ฐ€ load ์—ฐ์‚ฐ์„ ํ†ตํ•ด ๊ธฐ์กด ๋ฐฐ์—ด์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ SIMD Instruction๋“ค์€ immintrin.h ํ—ค๋”์—์„œ ์ฐธ๊ณ ํ•ด๋„ ์ข‹๊ณ , ์ด ๊ธ€ ์„œ๋‘์— ์ฒจ๋ถ€ํ•œ manual์„ ์ฐธ๊ณ ํ•ด๋„ ๋œ๋‹ค. ์—ฌ๋Ÿฌ ๋ฌธ์ œ๋“ค์— ์ ์šฉ์„ ์‹œ๋„ํ•˜๋‹ค๋ณด๋‹ˆ ์•ˆ๋˜๋Š” ์—ฐ์‚ฐ์€ modular ์—ฐ์‚ฐ ์ •๋„์ธ ๋“ฏ ํ•˜๋‹ค.

PS๋ฅผ ํ•˜๋ฉด์„œ ์ž๊พธ ํ‘๋งˆ๋ฒ•์— ๋น ์ง€๋ฉด ์•ˆ๋˜๋Š”๋ฐ, ์ œ์ผ ๊ณต๋ถ€ํ•˜๊ธฐ ์žฌ๋ฐŒ๋Š”๊ฒŒ ์ด๋Ÿฐ ํ‘๋งˆ๋ฒ•์ด๋‹ค. :)

๐Ÿท๏ธ ์ฃผ์ œ ๋ชฉ๋ก: